package IC.Visitors;

import IC.AST.*;
import IC.*;

import IC.TypeTable.*;
import IC.SymbolTable.*;

import java.util.*;
import IC.BinaryOps;


// check illegal use of undefined symbols
// Type checks
// other semantic checks: "break" and "continue" scope rules, "this" scope rules 

public class Checker implements Visitor {

	private GlobalSymbolTable global_Symbol_Table;  // IC.SymbolTable.GlobalSymbolTable
	private boolean isInStaticMethod = false;
	private int loopCounter = 0; //loopCounter


	public Checker(GlobalSymbolTable global_Symbol_Table){
		this.global_Symbol_Table = global_Symbol_Table;
	}


	//********************* Program ******************

	public Object visit(Program program) throws SemanticError {
		
		//recursively call to all classes
		for (ICClass iCClass: program.getClasses())
		{
			if (iCClass.accept(this) == null) 
				return null;
		}
		return true;
	}

	//*******************  ICClass  ****************	

	public Object visit(ICClass icClass) throws SemanticError 
	{
		// recursive calls to all methods of the class
		for(Method method: icClass.getMethods())
		{
			this.isInStaticMethod = false;
			if (method.accept(this) == null) 
				return null;
		}
		return true;
	}

	//*********** functions that will never called  ********

	public Object visit(PrimitiveType type) {
		return true;
	}

	public Object visit(Field field) {
		return true;
	}

	public Object visit(Formal formal) {
		return true;
	}

	public Object visit(UserType type) {
		return true;
	}
	//*******************************************


	//**********  VirtualMethod  - not static *****

	public Object visit(VirtualMethod method) throws SemanticError {
		isInStaticMethod = false;
		// recursive call to all statements in method
		for(Statement stmt: method.getStatements())
		{
			if (stmt.accept(this) == null)
				return null;
		}
		return true;
	}

	//***********  StaticMethod  ******************

	public Object visit(StaticMethod method) throws SemanticError {
		isInStaticMethod = true;
		// recursive call to all statements in method
		for(Statement stmt: method.getStatements())
		{
			if (stmt.accept(this) == null) 
				return null;
		}
		return true;
	}


	//***********  LibraryMethod  ******************

	public Object visit(LibraryMethod method) throws SemanticError {
		isInStaticMethod = false;
		// recursive call to all statements in method
		for(Statement stmt: method.getStatements())
		{
			if (stmt.accept(this) == null)
				return null;
		}
		return true;
	}

	//*********** Assignment  ***************

	// type check: the assignment type <= location type
	public Object visit(Assignment assignment) throws SemanticError
	{
		// (IC.TypeTable.Type)
		IC.TypeTable.Type locationType = (IC.TypeTable.Type) assignment.getVariable().accept(this);
		IC.TypeTable.Type assignmentType = (IC.TypeTable.Type) assignment.getAssignment().accept(this);

		if  ( (locationType !=null) &&  (assignmentType!=null) )
		{ //both are not null

			// type checking - assignment  must be of the same type or be subType of the location type
			if (!assignmentType.isSubTypeOf(locationType)){
				throw new SemanticError("Wrong Assignment Type, expect type: "+locationType.getName()+ " instead is: "+
						assignmentType.getName() , assignment.getLine());
				//return null;
			}
			else
				return true;
		}	
		else  // one of them are null
		{
			return null;
		}
	}

	//**************** CallStatement **********
	public Object visit(CallStatement callStatement) throws SemanticError {
		if (callStatement.getCall().accept(this) == null)
			return null;
		else 
			return true;
	}

	//****************** Break ****************

	// checks that in while loop
	public Object visit(Break breakStatement) throws SemanticError {
		//not in loop
		if (loopCounter == 0)
		{
			throw new SemanticError("'break' not inside a WHILE loop: "  + "break",
					breakStatement.getLine());
			//return null;
		}

		return true;
	}

	//****************** Continue ****************

	// checks that in while loop
	public Object visit(Continue continueStatement) throws SemanticError {
		if (loopCounter == 0){
			throw new SemanticError("'continue' not inside a WHILE loop: " +"continue",
					continueStatement.getLine());
			//return null;
		}

		return true;
	}

	//******************* While **********

	// type check: check that the condition type is of boolean type 

	public Object visit(While whileStmt) throws SemanticError {

		IC.TypeTable.Type type_of_condition = (IC.TypeTable.Type) whileStmt.getCondition().accept(this);
		if (type_of_condition == null) 
			return null;

		// type check: the condition is of boolean type 
		Boolean isSybType=false;
		try{
			isSybType=type_of_condition.isSubTypeOf(TypeTable.getType("boolean"));
		} 
		catch (SemanticError err)
		{
			// will never get here
			throw new SemanticError("TYPE ERROR in Checker - While visitor: " + err,
					whileStmt.getCondition().getLine());
		} 

		if (!isSybType)
		{
			throw new SemanticError("Condition in while statement must be of boolean type, instead is: " + type_of_condition.getName(),
					whileStmt.getCondition().getLine());
			//return null;
		}

		// check operation recursively
		loopCounter++;
		if (whileStmt.getOperation().accept(this) == null) {
			loopCounter--;
			return null;
		}
		loopCounter--;

		return true;
	}

	//****************** StatementsBlock ****************

	public Object visit(StatementsBlock statementsBlock) throws SemanticError {

		for(Statement st: statementsBlock.getStatements())
		{
			if (st.accept(this) == null)
				return null;
		}
		return true;
	}

	//**********************  Return *******************

	// type check that the return type is the same type or subtype of the enclosing method's type
	public Object visit(Return returnStmt)  throws SemanticError {

		IC.TypeTable.Type type_of_returned_value = null; 
		// don't have return type --> void
		if (!(returnStmt.hasValue()))
			try{
				type_of_returned_value = TypeTable.getType("void");
			} catch(SemanticError err)
			{   // will never get here
				throw new SemanticError("TYPE ERROR in Checker - Return visitor: " + err, returnStmt.getLine());
			} 
		else  // have return type
		{
			type_of_returned_value = (IC.TypeTable.Type) returnStmt.getValue().accept(this);
			if (type_of_returned_value == null) 
				return null;
		} 

		// type check that the return type is the same type or subtype of the enclosing method's type
		IC.TypeTable.Type returnType=null;
		
		returnType = ((BlockSymbolTable) returnStmt.getEnclosingScope()).lookupSymbolBlock("_retParam",returnStmt.getLine()).getType();

		if (!type_of_returned_value.isSubTypeOf(returnType))
		{
			//"Wrong Return Type" + returnStmt.getValue(), returnStmt
			throw new SemanticError("Wrong Return Type, expected: "+returnType.getName() + " instead is: "+ type_of_returned_value.getName(),
					returnStmt.getLine());
			// return null;
		}

		return true;
	}

	//********************  If  ****************

	//type check: check that the condition type is of boolean type 
	public Object visit(If ifStmt) throws SemanticError {
		IC.TypeTable.Type conditionType = (IC.TypeTable.Type) ifStmt.getCondition().accept(this);

		if (conditionType == null) 
			return null;

		// type check: the condition is of boolean type 
		Boolean isSubType=false;
		try
		{
			isSubType=conditionType.isSubTypeOf(TypeTable.getType("boolean"));
		} 
		catch (SemanticError err)
		{
			// will never get here
			throw new SemanticError("TYPE ERROR in Checker - If visitor: " + err, ifStmt.getLine());
		} 

		if (isSubType)
		{
			// check operation, elseOperation recursively
			if (ifStmt.getOperation().accept(this) == null) 
				
				return null;
			if (ifStmt.hasElse())
			{
				if (ifStmt.getElseOperation().accept(this) == null) 
					return null;
			}
		}
		else
		{ 
			// (!conditionType.isSubTypeOf(TypeTable.getType("boolean")))
			throw new SemanticError("If statement: condition not of boolean type, instead is: " + conditionType.getName(),
					ifStmt.getCondition().getLine());
			//return null;
		}
		return true;
	}



	//*********** LocalVariable *************

	// type check: check that the initValue type is a subtype of the local variable's type
	public Object visit(LocalVariable localVariable) throws SemanticError {

		if (localVariable.hasInitValue())
		{
			IC.TypeTable.Type initValueType = (IC.TypeTable.Type) localVariable.getInitValue().accept(this);
			
			if (initValueType != null) {
				IC.TypeTable.Type localVarType=null;
				
				localVarType = ((BlockSymbolTable) localVariable.getEnclosingScope()).lookupSymbolBlock(localVariable.getName(),localVariable.getLine()).getType();
				
				if (initValueType.isSubTypeOf(localVarType))
				{
					return true;
				}
				else
				{
					throw new SemanticError("LocalVariable: type expected: " + localVarType.getName() + " instead is: " + initValueType.getName(),
							localVariable.getLine());
					//return null;
				}
			}
			else
			{
				// (initValueType == null) 
				return null;
			}
		}
		return true;
	}


	//****************** ArrayLocation ************************

	// type check: check that the index is of int type 
	public Object visit(ArrayLocation location) throws SemanticError{

		// recursive call to array & recursive call to index
		IC.TypeTable.ArrayType arrayType = (IC.TypeTable.ArrayType) location.getArray().accept(this);
		IC.TypeTable.Type indexType = (IC.TypeTable.Type) location.getIndex().accept(this);
		if ((arrayType == null) ||  (indexType == null))
			return null;

		// type check: index is of int type 
		Boolean isSubType=false;
		try
		{
			isSubType=indexType.isSubTypeOf(TypeTable.getType("int"));
		} 
		catch (SemanticError err)
		{
			// will never get here
			throw new SemanticError("TYPE ERROR in Checker - ArrayLocation visitor: " + err, location.getLine());
		}

		if (isSubType)
		{
			return arrayType.getElemType();
		}
		else {
			throw new SemanticError("ArrayLocation: array index must be of type int, instead is: " + arrayType.getName(),
					location.getLine());
			//return null;
		}

	}
	////////////////////////////////// DOWN ///////////////////////////////////////////

	//******************  VariableLocation 	************

	public Object visit(VariableLocation location) throws SemanticError {

		if (!location.isExternal())
		{ // this location is not external
			// IC.TypeTable.Type
			return ((BlockSymbolTable) location.getEnclosingScope()).lookupSymbolBlock(location.getName(),location.getLine()).getType();
		}
		else   //  location is external
		{
			IC.TypeTable.Type locationType = (IC.TypeTable.Type) location.getLocation().accept(this);
			IC.SymbolTable.ClassSymbolTable classSymbT=null;

			if (locationType == null) 
				return null;

			// check if the location is a class type
			try{  
				//check if the external location is a user defined class
				TypeTable.getClassType(locationType.getName());
				// if location is a class, check that it has a field with this name	
				classSymbT = this.global_Symbol_Table.lookupClassSymbolTableRec(locationType.getName());
				
				
			} 
			catch(SemanticError err)
			{ // it isn't a user defined class 
				throw new SemanticError("VariableLocation of type "+locationType.getName()+" doesn't have the following field: " + location.getName(),
						location.getLine());
				//return null;
			}
			// return the type of the field
			// type of IC.SymbolTable.FieldSymbol
			
			
			return  classSymbT.lookupFieldSymbol(location.getName()).getType();

		} 
	}


	//**************** StaticCall ***********************

	// check:  the method is defined in the enclosing class
	// type checks: all argument correspond to the method's arguments types
	public Object visit(StaticCall call) throws SemanticError{
		IC.SymbolTable.ClassSymbolTable classSymbT = global_Symbol_Table.lookupClassSymbolTableRec(call.getClassName());  //(call.getClassName());
		
		// check if the class in the static call defined
		if (classSymbT != null)
		{
		
			MethodSymbol methodSym=null;  //IC.SymbolTable.MethodSymbol
			// check that the method is defined in enclosing class
			//TODO
			methodSym = classSymbT.lookupMethodSymbolInCurrScope(call.getName());
			
			if (methodSym==null )
			{throw new SemanticError("Method was not previously defined  : "+call.getName(),call.getLine());}
			if (!methodSym.isStaticMethod())
			{
				// is not static method
				throw new SemanticError("Method must be static, but isn't: " + call.getName(),
						call.getLine());
				//return null;
			}
			// method is static --> check arguments types
			Iterator<IC.TypeTable.Type> metArgTypeI = ((IC.TypeTable.MethodType) methodSym.getType()).getListTypes().iterator();
			for(Expression arg: call.getArguments()){
				IC.TypeTable.Type argType = (IC.TypeTable.Type) arg.accept(this);

				if (argType == null) return null;

				Boolean isSubType=null;
				try
				{
					isSubType=argType.isSubTypeOf(metArgTypeI.next());
				}
				catch (NoSuchElementException nsee)
				{ // method's parameters list is shorter than the arguments list
					throw new SemanticError("Too many arguments passed to method: " + call.getName(),
							call.getLine());
					//return null;
				}

				if (!isSubType)
				{ // wrong argument type sent to method
					throw new SemanticError("Type of argument passed to method is wrong: " + argType.getName(),
							call.getLine());
					//return null;
				}
			}

			if (metArgTypeI.hasNext()){
				// expects more parameters
				throw new SemanticError("Not enough arguments passed to method: " + call.getName(),
						call.getLine());
				//return null;
			}

			return ((IC.TypeTable.MethodType) methodSym.getType()).getReturnType();
		}
		
		else  //classSymbT == null
		{ 
			// class does not defined
			throw new SemanticError("Class wasn't not defined: " + call.getClassName(),
					call.getLine());
			//return null;
		}
	}

	//********************* VirtualCall  ***************************

	// check that the method is defined in the enclosing class
	//type checks: check that all arguments are corresponding to the method's arguments types
	public Object visit(VirtualCall call) throws SemanticError{

		IC.SymbolTable.ClassSymbolTable classSymbT = null;

		if (!call.isExternal())
		{ 
			classSymbT = ((BlockSymbolTable)call.getEnclosingScope()).getEnclosingClassSymbolTable();
			if (isInStaticMethod){
				// call are from static scope
				throw new SemanticError("Trying to call a local virtual method from a static scope: " + call.getName(),
						call.getLine());
				//return null;
			}
		}
		else // an external location
		{
			IC.TypeTable.Type locationType = (IC.TypeTable.Type) call.getLocation().accept(this); 
			if (locationType == null)
				return null; 

			classSymbT = global_Symbol_Table.lookupClassSymbolTableRec(locationType.getName());
			if (classSymbT == null){ 
				throw new SemanticError("The location type was not defined by user  : " + locationType.getName(),
						call.getLine());   
				//return null;
			}
		}


		MethodSymbol methodSym = null;
		methodSym = classSymbT.lookupMethodSymbol(call.getName());
		
	if (methodSym==null){
			throw new SemanticError(" method was not previously  declared: "+call.getName(),call.getLine());
		}

	//TODO
		if ((methodSym.isStaticMethod()) && (!classSymbT.isDeclaredInThisClass(call.getName(), 'm'))){
			throw new SemanticError("Trying to call static method virtually: " + call.getName(),
					call.getLine());
			//return null;
		}


		// method defined in class and is virtual
		//check arguments types of method
		Iterator<IC.TypeTable.Type> metArgTypeI = ((IC.TypeTable.MethodType) methodSym.getType()).getListTypes().iterator();
		for(Expression arg: call.getArguments()){
			IC.TypeTable.Type argType = (IC.TypeTable.Type) arg.accept(this);

			if (argType == null) 
				return null;
			Boolean isSubType=null;
			try
			{
				isSubType=argType.isSubTypeOf(metArgTypeI.next());
			}
			catch (NoSuchElementException err){
				throw new SemanticError("Too many arguments passed to method: " + call.getName(),
						call.getLine());
				//return null;
			}
			if (!isSubType)
			{ 
				new SemanticError("Type of argument passed to method is wrong: " + argType.getName(),
						call.getLine());
				//return null;
			}
		}

		if (metArgTypeI.hasNext())
		{
			// expect more parameters
			throw new SemanticError("Not enough arguments passed to method: " + call.getName(),
					call.getLine());
			//return null;
		}

		return ((MethodType) methodSym.getType()).getReturnType();  //IC.TypeTable.MethodType
	}

	//////////////////////////////// UP /////////////////////////////////////////////

	//************* This ********************

	// checks that 'this' is not inside a static method.
	public Object visit(This thisExpression) throws SemanticError {
		if (!isInStaticMethod) 
		{
			// not inside a static method
			return ((BlockSymbolTable) thisExpression.getEnclosingScope())
					.getEnclosingClassSymbolTable().getSymbolOfCurrTableInGlobal().getType();
		}
		else
		{ //  inside a static method
			throw new SemanticError("'this' is used in static method: " + "this",
					thisExpression.getLine());
			//return null;
		}
	}


	//*************  NewClass *************
	// checks that the class type exists
	public Object visit(NewClass newClass) throws SemanticError {
		//try if such class exist
		try{
			return  IC.TypeTable.TypeTable.getClassType(newClass.getName());
		}
		catch (SemanticError err)
		{ // No such class defined
			throw new SemanticError(err +"" , newClass.getLine());
			//return null;
		}
	}

	//******************  NewArray  ******************

	// checks that element type is a legal type.
	// checks that size is of int type.
	public Object visit(NewArray newArray) throws SemanticError {
		IC.TypeTable.Type type_of_elem = null;

		try 
		{
			type_of_elem = TypeTable.getType(newArray.getType().getName());
		}
		catch (SemanticError err)
		{ 
			// illegal array element type
			throw new SemanticError( err +"" , newArray.getLine());
			//return null;
		}

		IC.TypeTable.Type sizeType = (IC.TypeTable.Type) newArray.getSize().accept(this);
		if (sizeType != null)
		{
			Boolean isSubType=false;
			try
			{
				isSubType=sizeType.isSubTypeOf(TypeTable.getType("int"));
			} 
			catch (SemanticError err)
			{
				// will never get here
				throw new SemanticError("TYPE ERROR in Checker - NewArray visitor: " + err, newArray.getLine());
			} 
			if (isSubType)
			{
				try
				{
					return TypeTable.getType(type_of_elem.getName()+"[]");
				}
				catch (SemanticError err)
				{
					// will never get here
					throw new SemanticError("TYPE ERROR in Checker - NewArray visitor: " + err, newArray.getLine());
				} 
			}
			else
			{
				throw new SemanticError("Array size isn't of int type, instead is: " + sizeType.getName(),
						newArray.getLine());
				//return null;
			}

		}
		return null;
	}



	//*****************  Length  ************
	// check that length is used in array type
	public Object visit(Length length) throws SemanticError {
		IC.TypeTable.Type arrayType = (IC.TypeTable.Type) length.getArray().accept(this);

		if (arrayType != null) 
		{
			if (arrayType.getName().endsWith("[]"))
			{
				try 
				{ // check is O.K --> return int type.
					return TypeTable.getType("int");
				} 
				catch (SemanticError err) 
				{
					// will never get here
					throw new SemanticError("TYPE ERROR in Checker - Length visitor: " + err, length.getLine());
				} 
			}
			else
			{ //  
				throw new SemanticError("Length operator not on array type, instead on: "  + arrayType.getName(),
						length.getLine());
				//return null;			
			}
		}
		// arrayType == null
		return null;
	}

	// *************** MathBinaryOp ***************

	// check: types are legal:  + for int || string , other op for int  )
	public Object visit(MathBinaryOp binaryOp) throws SemanticError {
		IC.TypeTable.Type op1Type = (IC.TypeTable.Type) binaryOp.getFirstOperand().accept(this);
		IC.TypeTable.Type op2Type = (IC.TypeTable.Type) binaryOp.getSecondOperand().accept(this);
		if ((op1Type == null) || (op2Type == null))
			return null;
		// check that both operands are of the same type
		if (op1Type != op2Type){ 
			throw new SemanticError("Math binary operation is used on operands from different types: " +
					binaryOp.getOperator().getOperatorString(), binaryOp.getLine());
			//return null;
		}

		//  both operands are of the same type
		if (binaryOp.getOperator() == IC.BinaryOps.PLUS)
		{
			Boolean isSubType1=false;
			Boolean isSubType2=false;
			try
			{
				isSubType1=op1Type.isSubTypeOf(TypeTable.getType("int"));
				isSubType2=op1Type.isSubTypeOf(TypeTable.getType("string"));
			} 
			catch (SemanticError err)
			{
				// will never get here
				throw new SemanticError("TYPE ERROR1 in Checker - MathBinaryOP visitor: " + err, binaryOp.getLine());
			} 

			// operand type are not 'int' nor 'string'
			if (!isSubType1  && !isSubType2)   
			{
				throw new SemanticError(" Binary op '+' is used  on operands from illegal type: " + op1Type.getName(),
						binaryOp.getLine());
				//return null;
			}
		}
		else
		{// operator is one of "-","*","/","%"	
			Boolean isSubType1=false;
			try
			{
				isSubType1=op1Type.isSubTypeOf(TypeTable.getType("int"));
			} 
			catch (SemanticError err)
			{
				// will never get here
				throw new SemanticError("TYPE ERROR2 in Checker - MathBinaryOP visitor: " + err, binaryOp.getLine());
			} 
			if (!isSubType1)
			{
				// operands are of the same type --> enough to check only one of them
				throw new SemanticError("Math op (not '+') is used  on operands of non int type: "  + op1Type.getName(),
						binaryOp.getLine()); 
				//return null;
			}
		}
		return op2Type;
	}

	//*******************  LogicalBinaryOp  ****************

	//type check:  operands are of the correct type
	public Object visit(LogicalBinaryOp binaryOp) throws SemanticError {
		IC.TypeTable.Type op1Type = (IC.TypeTable.Type) binaryOp.getFirstOperand().accept(this);
		IC.TypeTable.Type op2Type = (IC.TypeTable.Type) binaryOp.getSecondOperand().accept(this);
		if ((op1Type == null) || (op2Type == null))
			return null;

		// check if one operand is a subtype of the other operand
		if (!op1Type.isSubTypeOf(op2Type) && !op2Type.isSubTypeOf(op1Type))
		{ 
			//two operand are from different sybtypes
			throw new SemanticError("Logical binary op. "+
					binaryOp.getOperator().getOperatorString()+" : type of one operand ISN'T a subtype of the other" ,binaryOp.getLine());
		}


		Boolean isSubType=false;

		//two operand are from the same sybtypes
		// operator is  "||" or "&&"
		if ((binaryOp.getOperator() == IC.BinaryOps.LAND) || (binaryOp.getOperator() == IC.BinaryOps.LOR))
		{
			try
			{
				isSubType=op1Type.isSubTypeOf(TypeTable.getType("boolean"));
			} 
			catch (SemanticError err)
			{
				// will never get here
				throw new SemanticError("TYPE ERROR1 in Checker - LogicalBinaryOP visitor: " + err, binaryOp.getLine());
			}

			if (!isSubType){
				throw new SemanticError("Logical binary op. 'LAND' or 'LOR' on non boolean operands: " + op1Type.getName(),
						binaryOp.getLine());
				//return null;
			}
		}
		else 
			if (binaryOp.getOperator() != IC.BinaryOps.EQUAL && binaryOp.getOperator() != IC.BinaryOps.NEQUAL)
			{
				try
				{
					isSubType=op1Type.isSubTypeOf(TypeTable.getType("int"));
				} 
				catch (SemanticError err)
				{
					// will never get here
					throw new SemanticError("TYPE ERROR2 in Checker - LogicalBinaryOP visitor: " + err, binaryOp.getLine());
				}

				if (!isSubType)
				{
					throw new SemanticError("Logical binary op. 'EQUAL' or 'NEQUAL' on non int operands: " + 
							op1Type.getName(), binaryOp.getLine());
					//return null;
				}
			}
		// operands are from legal types
		IC.TypeTable.Type returnType = null;
		try{
			returnType = TypeTable.getType("boolean");
		}
		catch (SemanticError err){
			// will never get here
			throw new SemanticError("TYPE ERROR1 in Checker - LogicalBinaryOP visitor: " + err, binaryOp.getLine());
		}
		return returnType;
	}


	//*****************  MathUnaryOp   ****************
	// checks that the operand is of int type.
	// if check is O.K --> return int type.
	public Object visit(MathUnaryOp unaryOp) throws SemanticError {

		IC.TypeTable.Type unaryOpType = (IC.TypeTable.Type) unaryOp.getOperand().accept(this);

		if (unaryOpType != null)
		{
			Boolean isSubType=false;
			try
			{
				isSubType=unaryOpType.isSubTypeOf(TypeTable.getType("int"));
			} 
			catch (SemanticError err)
			{
				// will never get here
				throw new SemanticError("TYPE ERROR in Checker - MathUnaryOp visitor: " + err, unaryOp.getLine());
			} 

			if (isSubType)
			{
				// unaryOpType is int
				return unaryOpType; // int
			}
			else
			{
				// opType is not a boolean
				throw new SemanticError("Trying to use Math unary operation on operand from non int type" + ": " +unaryOpType.getName(),
						unaryOp.getLine());
				//return null;
			}

		}

		//unaryOpType == null
		return null;
	}


	//**************  LogicalUnaryOp  ***************

	// checks that the operand is of boolean type.
	// if check is O.K --> return boolean type.
	public Object visit(LogicalUnaryOp unaryOp) throws SemanticError {

		IC.TypeTable.Type unaryOpType = (IC.TypeTable.Type) unaryOp.getOperand().accept(this);
		if (unaryOpType != null) {
			Boolean isSubType=false;
			try
			{
				isSubType=unaryOpType.isSubTypeOf(TypeTable.getType("boolean"));
			} 
			catch (SemanticError err)
			{
				// will never get here
				throw new SemanticError("TYPE ERROR in Checker - LogicalUnaryOp visitor: " + err, unaryOp.getLine());
			}


			if (isSubType)
			{
				// unaryOpType is  boolean
				return unaryOpType; // boolean
			}
			else
			{
				// opType is not a boolean
				throw new SemanticError("Trying to use Logical unary operation on operand from non boolean type" + ": " +unaryOpType.getName(),
						unaryOp.getLine());
				//return null;
			}		
		}

		//unaryOpType == null
		return null;
	}


	//*******************  Literal  *****************

	// returns  type of the literal
	public Object visit(Literal literal) throws SemanticError {
		//getType
		//IC.LiteralTypes
		LiteralTypes type = literal.getType(); 
		try
		{
			switch (type)
			{
			case INTEGER: return TypeTable.getType("int");
			case STRING: return TypeTable.getType("string");
			case TRUE: return TypeTable.getType("boolean");
			case FALSE: return TypeTable.getType("boolean");
			case NULL: return TypeTable.getType("null");
			}
		}
		catch(SemanticError err)
		{
			// will never get here
			throw new SemanticError("TYPE ERROR in Checker - Literal visitor: " + err, literal.getLine());
		} 
		return null;
	}


	//****************** ExpressionBlock **********************
	public Object visit(ExpressionBlock expressionBlock) throws SemanticError 
	{
		// will return null in case of error
		IC.TypeTable.Type type=(IC.TypeTable.Type) expressionBlock.getExpression().accept(this); 
		return type;
	}

}